/*
 * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/linkage.h>
#include <asm/smp_scu.h>
#include "hardware.h"

#define MMDC0_MDPDC		0x4
#define MMDC0_MDCF0			0x0c
#define MMDC0_MDCF1			0x10
#define MMDC0_MDMISC		0x18
#define MMDC0_MDSCR		0x1c
#define MMDC0_MAARCR		0x400
#define MMDC0_MAPSR			0x404
#define MMDC0_MADPCR0		0x410
#define MMDC0_MPZQHWCTRL	0x800
#define MMDC1_MPZQHWCTRL	0x4800
#define MMDC0_MPODTCTRL	0x818
#define MMDC1_MPODTCTRL	0x4818
#define MMDC0_MPDGCTRL0	0x83c
#define MMDC1_MPDGCTRL0	0x483c
#define MMDC0_MPMUR0		0x8b8
#define MMDC1_MPMUR0		0x48b8

#define CCM_CBCDR			0x14
#define CCM_CBCMR			0x18
#define CCM_CSCMR1			0x1c
#define CCM_CDHIPR			0x48

#define L2_CACHE_SYNC		0x730
#define PL310_AUX_CTRL          0x104
#define PL310_DCACHE_LOCKDOWN_BASE 0x900
#define PL310_AUX_16WAY_BIT	0x10000
#define PL310_LOCKDOWN_NBREGS   8
#define PL310_LOCKDOWN_SZREG    4
#define PL310_8WAYS_MASK        0x00FF
#define PL310_16WAYS_UPPERMASK  0xFF00

#define IMX6QP_REVISION_ID 0x630100
#define ANADIG_DIGPROG     0x260

.extern iram_tlb_phys_addr

.globl mx6_ddr3_freq_change_start
.globl mx6_ddr3_freq_change_end

	.align 3

	.macro is_mx6qp

	/* check if the SOC is i.MX6QP */
	ldr	r0, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
	ldr	r1, [r0, #ANADIG_DIGPROG]
	ldr	r2, =IMX6QP_REVISION_ID
	cmp	r1, r2

	.endm

	.macro	switch_to_528MHz

	/* check if periph_clk_sel is already set */
	ldr	r0, [r6, #CCM_CBCDR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	beq	set_ahb_podf_before_switch

	/* change periph_clk to be sourced from pll3_clk. */
	ldr	r0, [r6, #CCM_CBCMR]
	bic	r0, r0, #(3 << 12)
	str	r0, [r6, #CCM_CBCMR]

	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(0x38 << 20)
	str	r0, [r6, #CCM_CBCDR]

	/*
	 * set the AHB dividers before the switch,
	 * don't change AXI clock divider,
	 * set the MMDC_DIV=1, AXI_DIV = 2, AHB_DIV=4,
	 */
	ldr	r0, [r6, #CCM_CBCDR]
	ldr	r2, =0x3f1f00
	bic	r0, r0, r2
	orr	r0, r0, #0xd00
	orr	r0, r0, #(1 << 16)
	str	r0, [r6, #CCM_CBCDR]

wait_div_update528:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	wait_div_update528

	/* now switch periph_clk to pll3_main_clk. */
	ldr	r0, [r6, #CCM_CBCDR]
	orr	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch3:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch3

	b	switch_pre_periph_clk_528

set_ahb_podf_before_switch:
	/*
	 * set the MMDC_DIV=1, AXI_DIV = 2, AHB_DIV=4,
	 */
	ldr	r0, [r6, #CCM_CBCDR]
	ldr	r2, =0x3f1f00
	bic	r0, r0, r2
	orr	r0, r0, #0xd00
	orr	r0, r0, #(1 << 16)
	str	r0, [r6, #CCM_CBCDR]

wait_div_update528_1:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	wait_div_update528_1

switch_pre_periph_clk_528:

	/* now switch pre_periph_clk to PLL2_528MHz. */
	ldr	r0, [r6, #CCM_CBCMR]
	bic	r0, r0, #(0xc << 16)
	str	r0, [r6, #CCM_CBCMR]

	/* now switch periph_clk back. */
	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch4:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch4

	.endm

	.macro	switch_to_400MHz

	/* check if periph_clk_sel is already set. */
	ldr	r0, [r6, #CCM_CBCDR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	beq	set_ahb_podf_before_switch1

	/* change periph_clk to be sourced from pll3_clk. */
	ldr	r0, [r6, #CCM_CBCMR]
	bic	r0, r0, #(3 << 12)
	str	r0, [r6, #CCM_CBCMR]

	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(0x38 << 24)
	str	r0, [r6, #CCM_CBCDR]

	/* now switch periph_clk to pll3_main_clk. */
	ldr	r0, [r6, #CCM_CBCDR]
	orr	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch5:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch5

	b	switch_pre_periph_clk_400

set_ahb_podf_before_switch1:
	/*
	 * set the MMDC_DIV=1, AXI_DIV = 2, AHB_DIV=4,
	 */
	ldr	r0, [r6, #CCM_CBCDR]
	ldr	r2, =0x3f1f00
	bic	r0, r0, r2
	orr	r0, r0, #(0x9 << 8)
	orr	r0, r0, #(1 << 16)
	str	r0, [r6, #CCM_CBCDR]

wait_div_update400_1:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	wait_div_update400_1

switch_pre_periph_clk_400:

	/* now switch pre_periph_clk to PFD_400MHz. */
	ldr	r0, [r6, #CCM_CBCMR]
	bic	r0, r0, #(0xc << 16)
	orr	r0, r0, #(0x4 << 16)
	str	r0, [r6, #CCM_CBCMR]

	/* now switch periph_clk back. */
	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch6:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch6

	/*
	 * change AHB divider so that we are at 400/3=133MHz.
	 * don't change AXI clock divider.
	 * set the MMDC_DIV=1, AXI_DIV=2, AHB_DIV=3,
	 */
	ldr	r0, [r6, #CCM_CBCDR]
	ldr	r2, =0x3f1f00
	bic	r0, r0, r2
	orr	r0, r0, #(0x9 << 8)
	orr	r0, r0, #(1 << 16)
	str	r0, [r6, #CCM_CBCDR]

wait_div_update400_2:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	wait_div_update400_2

	.endm

	.macro	switch_to_50MHz

	/* check if periph_clk_sel is already set. */
	ldr	r0, [r6, #CCM_CBCDR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	beq	switch_pre_periph_clk_50

	/*
	 * set the periph_clk to be sourced from PLL2_PFD_200M
	 * change periph_clk to be sourced from pll3_clk.
	 * ensure PLL3 is the source and set the divider to 1.
	 */
	ldr	r0, [r6, #CCM_CBCMR]
	bic	r0, r0, #(0x3 << 12)
	str	r0, [r6, #CCM_CBCMR]

	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(0x38 << 24)
	str	r0, [r6, #CCM_CBCDR]

	/* now switch periph_clk to pll3_main_clk. */
	ldr	r0, [r6, #CCM_CBCDR]
	orr	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch_50:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch_50

switch_pre_periph_clk_50:

	/* now switch pre_periph_clk to PFD_200MHz. */
	ldr	r0, [r6, #CCM_CBCMR]
	orr	r0, r0, #(0xc << 16)
	str	r0, [r6, #CCM_CBCMR]

	/*
	 * set the MMDC_DIV=4, AXI_DIV = 4, AHB_DIV=8,
	 */
	ldr	r0, [r6, #CCM_CBCDR]
	ldr	r2, =0x3f1f00
	bic	r0, r0, r2
	orr	r0, r0, #(0x18 << 16)
	orr	r0, r0, #(0x3 << 16)

	/*
	 * if changing AHB divider remember to change
	 * the IPGPER divider too below.
	 */
	orr	r0, r0, #0x1d00
	str	r0, [r6, #CCM_CBCDR]

wait_div_update_50:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	wait_div_update_50

	/* now switch periph_clk back. */
	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch2:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch2

	.endm

	.macro	switch_to_24MHz
	/*
	 * change the freq now try setting DDR to 24MHz.
	 * source it from the periph_clk2 ensure the
	 * periph_clk2 is sourced from 24MHz and the
	 * divider is 1.
	 */

	ldr	r0, [r6, #CCM_CBCMR]
	bic	r0, r0, #(0x3 << 12)
	orr	r0, r0, #(1 << 12)
	str	r0, [r6, #CCM_CBCMR]

	ldr	r0, [r6, #CCM_CBCDR]
	bic	r0, r0, #(0x38 << 24)
	str	r0, [r6, #CCM_CBCDR]

	/* now switch periph_clk to 24MHz. */
	ldr	r0, [r6, #CCM_CBCDR]
	orr	r0, r0, #(1 << 25)
	str	r0, [r6, #CCM_CBCDR]

periph_clk_switch1:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	periph_clk_switch1

	/* change all the dividers to 1. */
	ldr	r0, [r6, #CCM_CBCDR]
	ldr	r2, =0x3f1f00
	bic	r0, r0, r2
	orr	r0, r0, #(1 << 8)
	str	r0, [r6, #CCM_CBCDR]

	/* Wait for the divider to change. */
wait_div_update:
	ldr	r0, [r6, #CCM_CDHIPR]
	cmp	r0, #0
	bne	wait_div_update

	.endm

	.macro	disable_l1_dcache

	/*
	 * Flush all data from the L1 data cache before disabling
	 * SCTLR.C bit.
	 */
	push	{r0 - r11, lr}

	ldr	r7, =v7_flush_kern_cache_all
	mov	lr, pc
	mov	pc, r7
	pop	{r0 - r11, lr}

	/* disable d-cache */
	mrc	p15, 0, r6, c1, c0, 0
	bic	r6, r6, #0x4
	mcr	p15, 0, r6, c1, c0, 0
	dsb
	isb

	push	{r0 - r11, lr}

	ldr	r7, =v7_flush_kern_cache_all
	mov	lr, pc
	mov	pc, r7
	pop	{r0 - r11, lr}

	.endm

/*
 *  mx6_ddr3_freq_change
 *
 *  idle the processor (eg, wait for interrupt).
 *  make sure DDR is in self-refresh.
 *  IRQs are already disabled.
 */
ENTRY(mx6_ddr3_freq_change)

mx6_ddr3_freq_change_start:
	stmfd	sp!, {r4-r12}

	/*
	 * r5 -> mmdc_base
	 * r6 -> ccm_base
	 * r7 -> iomux_base
	 * r12 -> l2_base
	 */
	mov	r4, r0
	mov	r8, r1
	mov	r9, r2
	mov	r11, r3

	/* flush the TLB */
	ldr	r6, =0x0
	mcr	p15, 0, r6, c8, c3, 0

	ldr	r6, =iram_tlb_phys_addr
	ldr	r7, [r6]

	/*
	  * Need to flush and disable L1 before
	  * disabling L2, we need data to
	  * coherent. Flushing L1 pushes
	  * everyhting to L2. We sync L2 later, but
	  * it can still have dirty lines.
	  * While exiting, we need to enable L2 first
	  * and then L1.
	  */
	disable_l1_dcache

#ifdef CONFIG_CACHE_L2X0
	/*
	 * Make sure the L2 buffers are drained.
	 * Sync operation on L2 drains the buffers.
	 */
	ldr	r12, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)

	/* Wait for background operations to complete. */
wait_for_l2_to_idle:
	ldr	r1, [r12, #L2_CACHE_SYNC]
	cmp	r1, #0x0
	bne	wait_for_l2_to_idle

	mov	r1, #0x0
	str	r1, [r12, #L2_CACHE_SYNC]

	dsb
	isb

	ldr	r1, [r12, #PL310_AUX_CTRL]
	tst	r1, #PL310_AUX_16WAY_BIT
	mov	r1, #PL310_8WAYS_MASK
	orrne	r1, #PL310_16WAYS_UPPERMASK
	mov	r6, #PL310_LOCKDOWN_NBREGS
	add	r5, r12, #PL310_DCACHE_LOCKDOWN_BASE
1:	/* lock Dcache and Icache */
	str	r1, [r5], #PL310_LOCKDOWN_SZREG
	str	r1, [r5], #PL310_LOCKDOWN_SZREG
	subs	r6, r6, #1
	bne	1b
#endif

	/*
	 * To ensure no page table walks occur in DDR, we
	 * have a another page table stored in IRAM that only
	 * contains entries pointing to IRAM, AIPS1 and AIPS2.
	 * We need to set the TTBR1 to the new IRAM TLB.
	 * Do the following steps:
	 * 1. Flush the Branch Target Address Cache (BTAC)
	 * 2. Set TTBR1 to point to IRAM page table.
	 * 3. Disable page table walks in TTBR0 (PD0 = 1)
	 * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
	 *     and 2-4G is translated by TTBR1.
	 */


	/* Now switch the TTBR. */
	/* Disable Branch Prediction, Z bit in SCTLR. */
	mrc	p15, 0, r6, c1, c0, 0
	bic	r6, r6, #0x800
	mcr	p15, 0, r6, c1, c0, 0

	/* Flush the Branch Target Address Cache (BTAC) */
	ldr	r6, =0x0
	mcr	p15, 0, r6, c7, c1, 6

	dsb
	isb

	/* Store the IRAM table in TTBR1 */
	mcr	p15, 0, r7, c2, c0, 1

	/* Read TTBCR and set PD0=1, N = 1 */
	mrc	p15, 0, r6, c2, c0, 2
	orr	r6, r6, #0x11
	mcr	p15, 0, r6, c2, c0, 2

	dsb
	isb

	/* flush the TLB */
	ldr	r6, =0x0
	mcr	p15, 0, r6, c8, c3, 0

	dsb
	isb


	ldr	r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
	ldr	r6, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
	ldr	r7, =IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR)

	/* Read the Original MU delay value */
	ldr	r1, [r5, #MMDC0_MPMUR0]
	mov	r10, r1, lsr #16
	ldr	r1, =0x3ff
	and	r10, r10, r1

	/* disable automatic power saving. */
	ldr	r0, [r5, #MMDC0_MAPSR]
	orr	r0, r0, #0x01
	str	r0, [r5, #MMDC0_MAPSR]

	/* disable MMDC power down timer. */
	ldr	r0, [r5, #MMDC0_MDPDC]
	bic	r0, r0, #(0xff << 8)
	str	r0, [r5, #MMDC0_MDPDC]

	/* delay for a while */
	ldr	r1, =4
delay1:
	ldr	r2, =0
cont1:
	ldr	r0, [r5, r2]
	add	r2, r2, #4
	cmp	r2, #16
	bne	cont1
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	delay1

	/* set CON_REG */
	ldr	r0, =0x8000
	str	r0, [r5, #MMDC0_MDSCR]
poll_conreq_set_1:
	ldr	r0, [r5, #MMDC0_MDSCR]
	and	r0, r0, #(0x4 << 12)
	cmp	r0, #(0x4 << 12)
	bne	poll_conreq_set_1

	/*
	 * if requested frequency is great than
	 * 300MHz, skip setting bypass adopt mode.
	 */
	ldr	r1, =300000000
	cmp	r4, r1
	bge	1f

	is_mx6qp
	bne	1f
	/* Switch to adopt mode, set MMDC0_MAARCR bit25~26 to 2b'01 */
	ldr	r0, [r5, #MMDC0_MAARCR]
	bic	r0, r0, #(0x3 << 25)
	orr	r0, #(0x01 << 25)
	str	r0 , [r5, #MMDC0_MAARCR]
1:
	ldr	r0, =0x00008050
	str	r0, [r5, #MMDC0_MDSCR]
	ldr	r0, =0x00008058
	str	r0, [r5, #MMDC0_MDSCR]

	/*
	 * if requested frequency is greater than
	 * 300MHz go to DLL on mode.
	 */
	ldr	r1, =300000000
	cmp	r4, r1
	bge	dll_on_mode

dll_off_mode:

	/* if DLL is currently on, turn it off. */
	cmp	r9, #1
	beq	continue_dll_off_1

	ldr	r0, =0x00018031
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x00018039
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r1, =10
delay1a:
	ldr	r2, =0
cont1a:
	ldr	r0, [r5, r2]
	add	r2, r2, #4
	cmp	r2, #16
	bne	cont1a
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	delay1a

continue_dll_off_1:
	/* set DVFS - enter self refresh mode */
	ldr	r0, [r5, #MMDC0_MAPSR]
	orr	r0, r0, #(1 << 21)
	str	r0, [r5, #MMDC0_MAPSR]

	/* de-assert con_req */
	mov	r0, #0x0
	str	r0, [r5, #MMDC0_MDSCR]

poll_dvfs_set_1:
	ldr	r0, [r5, #MMDC0_MAPSR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	bne	poll_dvfs_set_1

	ldr	r1, =24000000
	cmp	r4, r1
	beq	switch_freq_24

	switch_to_50MHz
	b	continue_dll_off_2

switch_freq_24:
	switch_to_24MHz

continue_dll_off_2:

	/* set SBS - block ddr accesses */
	ldr	r0, [r5, #MMDC0_MADPCR0]
	orr	r0, r0, #(1 << 8)
	str	r0, [r5, #MMDC0_MADPCR0]

	/* clear DVFS - exit from self refresh mode */
	ldr	r0, [r5, #MMDC0_MAPSR]
	bic	r0, r0, #(1 << 21)
	str	r0, [r5, #MMDC0_MAPSR]

poll_dvfs_clear_1:
	ldr	r0, [r5, #MMDC0_MAPSR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	beq	poll_dvfs_clear_1

	/* if DLL was previously on, continue DLL off routine. */
	cmp    	r9, #1
	beq 	continue_dll_off_3

	ldr	r0, =0x00018031
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x00018039
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x08208030
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x08208038
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x00088032
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x0008803A
	str	r0, [r5, #MMDC0_MDSCR]

	/* delay for a while. */
	ldr	r1, =4
delay_1:
	ldr	r2, =0
cont_1:
	ldr	r0, [r5, r2]
	add	r2, r2, #4
	cmp	r2, #16
	bne	cont_1
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	delay_1

	ldr	r0, [r5, #MMDC0_MDCF0]
	bic	r0, r0, #0xf
	orr	r0, r0, #0x3
	str	r0, [r5, #MMDC0_MDCF0]

	ldr	r0, [r5, #MMDC0_MDCF1]
	bic	r0, r0, #0x7
	orr	r0, r0, #0x4
	str	r0, [r5, #MMDC0_MDCF1]

	ldr	r0, [r5, #MMDC0_MDMISC]
	bic	r0, r0, #(0x3 << 16) /* walat = 0x1 */
	orr	r0, r0, #(0x1 << 16)
	bic	r0, r0, #(0x7 << 6)  /* ralat = 0x2 */
	orr	r0, r0, #(0x2 << 6)
	str	r0, [r5, #MMDC0_MDMISC]

	/* enable dqs pull down in the IOMUX. */
	ldr	r1, [r11]
	add	r11, r11, #8
	ldr	r2, =0x3028
update_iomux:
	ldr	r0, [r11, #0x0]
	ldr	r3, [r7, r0]
	bic	r3, r3, r2
	orr	r3, r3, #(0x3 << 12)
	orr	r3, r3, #0x28
	str	r3, [r7, r0]
	add	r11, r11, #8
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	update_iomux

	/*  ODT disabled. */
	ldr	r0, =0x0
	ldr	r2, =MMDC0_MPODTCTRL
	str	r0, [r5, r2]
	ldr	r2, =MMDC1_MPODTCTRL
	str	r0, [r5, r2]

	/* DQS gating disabled. */
	ldr	r2, =MMDC0_MPDGCTRL0
	ldr	r0, [r5, r2]
	orr	r0, r0, #(1 << 29)
	str	r0, [r5, r2]

	ldr	r2, =MMDC1_MPDGCTRL0
	ldr	r0, [r5, r2]
	orr	r0, r0, #(0x1 << 29)
	str	r0, [r5, r2]

	/* Add workaround for ERR005778.*/
	/* double the original MU_UNIT_DEL_NUM. */
	lsl	r10, r10, #1

	/* Bypass the automatic MU by setting the mu_byp_en */
	ldr	r2, [r5, #MMDC0_MPMUR0]
	orr	r2, r2, #0x400
	orr	r2, r2, r10
	str	r2, [r5, #MMDC0_MPMUR0]
	ldr	r0, =MMDC1_MPMUR0
	str	r2, [r5, r0]

	/* Now perform a force measure */
	ldr	r0, [r5, #MMDC0_MPMUR0]
	orr	r0, r0, #0x800
	str	r0, [r5, #MMDC0_MPMUR0]
	ldr	r2, =MMDC1_MPMUR0
	str	r0, [r5, r2]
	/* Wait for FRC_MSR to clear. */
1:
	ldr	r0, [r5, #MMDC0_MPMUR0]
	and	r0, r0, #0x800
	ldr	r1, [r5, r2]
	and	r1, r1, #0x800
	orr	r0, r0, r1
	cmp	r0, #0x0
	bne	1b

continue_dll_off_3:
	/* clear SBS - unblock accesses to DDR. */
	ldr	r0, [r5, #MMDC0_MADPCR0]
	bic	r0, r0, #(0x1 << 8)
	str	r0, [r5, #MMDC0_MADPCR0]

	mov	r0, #0x0
	str	r0, [r5, #MMDC0_MDSCR]
poll_conreq_clear_1:
	ldr	r0, [r5, #MMDC0_MDSCR]
	and	r0, r0, #(0x4 << 12)
	cmp	r0, #(0x4 << 12)
	beq	poll_conreq_clear_1

	b	done

dll_on_mode:
	/* assert DVFS - enter self refresh mode. */
	ldr	r0, [r5, #MMDC0_MAPSR]
	orr	r0, r0, #(1 << 21)
	str	r0, [r5, #MMDC0_MAPSR]

	/* de-assert CON_REQ. */
	mov	r0, #0x0
	str	r0, [r5, #MMDC0_MDSCR]

	/* poll DVFS ack. */
poll_dvfs_set_2:
	ldr	r0, [r5, #MMDC0_MAPSR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	bne	poll_dvfs_set_2

	ldr	r1, =528000000
	cmp	r4, r1
	beq	switch_freq_528

	switch_to_400MHz

	b	continue_dll_on

switch_freq_528:
	switch_to_528MHz

continue_dll_on:

	/* set SBS step-by-step mode. */
	ldr	r0, [r5, #MMDC0_MADPCR0]
	orr	r0, r0, #( 1 << 8)
	str	r0, [r5, #MMDC0_MADPCR0]

	/* clear DVFS - exit self refresh mode. */
	ldr	r0, [r5, #MMDC0_MAPSR]
	bic	r0, r0, #(1 << 21)
	str	r0, [r5, #MMDC0_MAPSR]

poll_dvfs_clear_2:
	ldr	r0, [r5, #MMDC0_MAPSR]
	and	r0, r0, #(1 << 25)
	cmp	r0, #(1 << 25)
	beq	poll_dvfs_clear_2

	/* if DLL is currently off, turn it back on. */
	cmp	r9, #0
	beq	update_calibration_only

	/* issue zq calibration command */
	ldr	r0, [r5, #MMDC0_MPZQHWCTRL]
	orr	r0, r0, #0x3
	str	r0, [r5, #MMDC0_MPZQHWCTRL]
	ldr	r2, =MMDC1_MPZQHWCTRL
	str	r0, [r5, r2]

	/* enable DQS gating. */
	ldr	r2, =MMDC0_MPDGCTRL0
	ldr	r0, [r5, r2]
	bic	r0, r0, #(1 << 29)
	str	r0, [r5, r2]

	ldr	r2, =MMDC1_MPDGCTRL0
	ldr	r0, [r5, r2]
	bic	r0, r0, #(1 << 29)
	str	r0, [r5, r2]

	/* force measure. */
	ldr	r0, =0x00000800
	str	r0, [r5, #MMDC0_MPMUR0]
	ldr	r2, =MMDC1_MPMUR0
	str	r0, [r5, r2]

	/* Wait for FRC_MSR to clear. */
1:
	ldr	r0, [r5, #MMDC0_MPMUR0]
	and	r0, r0, #0x800
	ldr	r1, [r5, r2]
	and	r1, r1, #0x800
	orr	r0, r0, r1
	cmp	r0, #0x0
	bne	1b

	/* disable dqs pull down in the IOMUX. */
	ldr	r1, [r11]
	add	r11, r11, #8
update_iomux1:
	ldr	r0, [r11, #0x0]
	ldr	r3, [r11, #0x4]
	str	r3, [r7, r0]
	add	r11, r11, #8
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	update_iomux1

	/* config MMDC timings to 528MHz. */
	ldr	r9, [r8]
	add	r8, r8, #8
	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	/* configure ddr devices to dll on, odt. */
	ldr	r0, =0x00048031
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x00048039
	str	r0, [r5, #MMDC0_MDSCR]

	/* delay for while. */
	ldr	r1, =4
delay7:
	ldr	r2, =0
cont7:
	ldr	r0, [r5, r2]
	add	r2, r2, #4
	cmp	r2, #16
	bne	cont7
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	delay7

	/* reset dll. */
	ldr	r0, =0x09408030
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x09408038
	str	r0, [r5, #MMDC0_MDSCR]

	/* delay for while. */
	ldr	r1, =100
delay8:
	ldr	r2, =0
cont8:
	ldr	r0, [r5, r2]
	add	r2, r2, #4
	cmp	r2, #16
	bne	cont8
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	delay8

	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	ldr	r0, =0x00428031
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x00428039
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	/* issue a zq command. */
	ldr	r0, =0x04008040
	str	r0, [r5, #MMDC0_MDSCR]

	ldr	r0, =0x04008048
	str	r0, [r5, #MMDC0_MDSCR]

	/* MMDC ODT enable. */
	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8

	ldr	r2, =0x4818
	str	r3, [r5, r2]

	/* delay for while. */
	ldr	r1, =40
delay15:
	ldr	r2, =0
cont15:
	ldr	r0, [r5, r2]
	add	r2, r2, #4
	cmp	r2, #16
	bne	cont15
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	delay15

	/* enable MMDC power down timer. */
	ldr	r0, [r5, #MMDC0_MDPDC]
	orr	r0, r0, #(0x55 << 8)
	str	r0, [r5, #MMDC0_MDPDC]

	b	update_calibration

update_calibration_only:
	ldr	r1, [r8]
	sub	r1, r1, #7
	add	r8, r8, #64
	b	update_calib

update_calibration:
	/* write the new calibration values. */
	mov	r1, r9
	sub	r1, r1, #7

update_calib:
	ldr	r0, [r8, #0x0]
	ldr	r3, [r8, #0x4]
	str	r3, [r5, r0]
	add	r8, r8, #8
	sub	r1, r1, #1
	cmp	r1, #0
	bgt	update_calib

	/* perform a force measurement. */
	ldr	r0, =0x800
	str	r0, [r5, #MMDC0_MPMUR0]
	ldr	r2, =MMDC1_MPMUR0
	str	r0, [r5, r2]

	/* Wait for FRC_MSR to clear. */
1:
	ldr	r0, [r5, #MMDC0_MPMUR0]
	and	r0, r0, #0x800
	ldr	r1, [r5, r2]
	and	r1, r1, #0x800
	orr	r0, r0, r1
	cmp	r0, #0x0
	bne	1b

	/* clear SBS - unblock DDR accesses. */
	ldr	r0, [r5, #MMDC0_MADPCR0]
	bic	r0, r0, #(1 << 8)
	str	r0, [r5, #MMDC0_MADPCR0]

	is_mx6qp
	bne	3f
	/*
	 * Switch back to adopt_bp mode, set MMDC0_MAARCR
	 * bit25~26 to 2b'10.
	 */
	ldr	r0, [r5, #MMDC0_MAARCR]
	bic	r0, r0, #(0x3 << 25)
	orr	r0, r0, #(0x2 << 25)
	str	r0, [r5, #MMDC0_MAARCR]
3:
	mov	r0, #0x0
	str	r0, [r5, #MMDC0_MDSCR]
poll_conreq_clear_2:
	ldr	r0, [r5, #MMDC0_MDSCR]
	and	r0, r0, #(0x4 << 12)
	cmp	r0, #(0x4 << 12)
	beq	poll_conreq_clear_2

done:
	/* MMDC0_MAPSR adopt power down enable. */
	ldr	r0, [r5, #MMDC0_MAPSR]
	bic	r0, r0, #0x01
	str	r0, [r5, #MMDC0_MAPSR]

#ifdef CONFIG_CACHE_L2X0
	ldr	r1, [r12, #PL310_AUX_CTRL]
	tst	r1, #PL310_AUX_16WAY_BIT
	mov	r6, #PL310_LOCKDOWN_NBREGS
	mov	r1, #0x00	/* 8 ways mask */
	orrne	r1, #0x0000	/* 16 ways mask */
	add	r5, r12, #PL310_DCACHE_LOCKDOWN_BASE
1:	/* lock Dcache and Icache */
	str	r1, [r5], #PL310_LOCKDOWN_SZREG
	str	r1, [r5], #PL310_LOCKDOWN_SZREG
	subs	r6, r6, #1
	bne	1b

	isb
	dsb
#endif

	/* Enable L1 data cache. */
	mrc	p15, 0, r6, c1, c0, 0
	orr	r6, r6, #0x4
	mcr	p15, 0, r6, c1, c0, 0

	/* Restore the TTBCR */
	dsb
	isb

	/* Read TTBCR and set PD0=0, N = 0 */
	mrc	p15, 0, r6, c2, c0, 2
	bic	r6, r6, #0x11
	mcr	p15, 0, r6, c2, c0, 2
	dsb
	isb

	/* flush the TLB */
	ldr	r6, =0x0
	mcr	p15, 0, r6, c8, c3, 0

	dsb
	isb

	/* Enable Branch Prediction, Z bit in SCTLR. */
	mrc	p15, 0, r6, c1, c0, 0
	orr	r6, r6, #0x800
	mcr	p15, 0, r6, c1, c0, 0

	isb

	/* Flush the Branch Target Address Cache (BTAC) */
	ldr	r6, =0x0
	mcr	p15, 0, r6, c7, c1, 6
	isb
	dsb

	/* restore registers */
	ldmfd	sp!, {r4-r12}
	mov	pc, lr

	/*
	 * Add ltorg here to ensure that all
	 * literals are stored here and are
	 * within the text space.
	 */
	.ltorg
mx6_ddr3_freq_change_end:
